package com.winbaoxian.crawler.core.crawlertask.executor.ajax;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.winbaoxian.crawler.constant.WinCrawlerConstant;
import com.winbaoxian.crawler.core.common.ParamsExecutor;
import com.winbaoxian.crawler.core.crawlertask.executor.IStepExecutor;
import com.winbaoxian.crawler.core.crawlertask.model.ajax.AbstractAjaxStep;
import com.winbaoxian.crawler.core.crawlertask.model.ajax.Ocr;
import com.winbaoxian.crawler.enums.RequestContentType;
import com.winbaoxian.crawler.enums.RequestMethod;
import com.winbaoxian.crawler.enums.SystemConfigKey;
import com.winbaoxian.crawler.model.core.TaskContext;
import com.winbaoxian.crawler.utils.HttpUtils;
import com.winbaoxian.crawler.utils.JsonUtils;
import com.winbaoxian.crawler.utils.ProxyUtils;
import com.winbaoxian.crawler.utils.WinCrawlerLogUtils;
import io.restassured.RestAssured;
import io.restassured.http.ContentType;
import io.restassured.response.Response;
import io.restassured.specification.RequestSpecification;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;

import javax.annotation.Resource;
import java.io.IOException;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import static io.restassured.RestAssured.given;
import static io.restassured.config.EncoderConfig.encoderConfig;


@Slf4j
public abstract class AbstractAjaxStepExecutor<T extends AbstractAjaxStep> implements IStepExecutor<T> {

    @Resource
    private ParamsExecutor paramsExecutor;

    @Override
    public void execute(T step, TaskContext context) throws Exception {
        JSONObject response = preExecute(step, context);
        Object tplRet = extractDataWithTpl(step, context, response);
        Object ret = doExecute(step, context, tplRet);
        //存储结果
        String stepLogPrefix = WinCrawlerLogUtils.INSTANCE.getStepLogPrefix(step, context);
        if (StringUtils.isNotBlank(step.getAlias())) {
            context.getGlobalParams().put(step.getAlias(), ret);
            log.info("[{}]globalParams,add key:{},values:{}", stepLogPrefix, step.getAlias(), JsonUtils.INSTANCE.toJSONString(ret));
        }
        if (step.getDelayTime() != null) {
            log.info("[{}]DelayTime, time: {}", stepLogPrefix, step.getDelayTime());
            Thread.sleep(step.getDelayTime());
        }
    }

    protected abstract Object doExecute(T step, TaskContext context, Object result) throws Exception;

    private JSONObject preExecute(T step, TaskContext context) throws Exception {
        RequestSpecification specification = given().log().everything();
        String stepLogPrefix = WinCrawlerLogUtils.INSTANCE.getStepLogPrefix(step, context);
        Response response = null;
        int retryTimes = 3;
        int index = 0;
        boolean needUseProxy = false;
        while (index++ <= retryTimes) {
            //proxy
            try {
                log.info("[{}]尝试第{}次发送请求", stepLogPrefix, index);
                if (needUseProxy) {
                    String useProxyKey = SystemConfigKey.ajax_use_proxy.name();
                    Map<String, String> configMap = context.getSystemConfig();
                    if (configMap.containsKey(useProxyKey) && configMap.get(useProxyKey).equals(WinCrawlerConstant.SYSTEM_CONFIG_VALUE_TRUE)) {
                        String[] proxyValues = ProxyUtils.INSTANCE.getAjaxProxyIpPort();
                        if (ArrayUtils.isNotEmpty(proxyValues)) {
                            specification.proxy(proxyValues[0], Integer.parseInt(proxyValues[1]));
                        }
                    }
                }
                //ContentType
                RequestContentType requestContentType = step.getRequestContentType();
                if (requestContentType != null) {
                    specification.contentType(requestContentType.getName());
                } else {
                    specification.contentType(ContentType.TEXT);
                }
                //默认UTF-8编码
                specification.config(RestAssured.config().encoderConfig(encoderConfig().defaultContentCharset(Charset.defaultCharset())));
                //RequestHeader
                Map<String, String> requestHeader = step.getRequestHeader();
                if (MapUtils.isNotEmpty(requestHeader)) {
                    specification.headers(requestHeader);
                }
                //RequestParams
                Map<String, String> requestParams = step.getRequestParams();
                if (MapUtils.isNotEmpty(requestParams)) {
                    specification.queryParams(requestParams);
                }
                //RequestBody
                String requestBody = step.getRequestBody();
                if (StringUtils.isNotBlank(requestBody)) {
                    if (Arrays.asList(RequestContentType.XML, RequestContentType.JSON, RequestContentType.TEXT).contains(requestContentType)) {
                        specification.body(requestBody);
                    } else if (RequestContentType.FORM.equals(requestContentType)) {
                        JSONObject formParams = JSON.parseObject(requestBody);
                        specification.formParams(formParams);
                    } else if (RequestContentType.MULTIPART.equals(requestContentType)) {
                        JSONObject multiPartParams = JSON.parseObject(requestBody);
                        if (multiPartParams != null || multiPartParams.size() == 1) {
                            Set<String> keys = multiPartParams.keySet();
                            String k = (String) CollectionUtils.get(keys, 0);
                            String v = multiPartParams.getString(k);
                            String mimeType = RequestContentType.BINARY.getName();
                            String fileName = v.lastIndexOf("/") >= 0 ? v.substring(v.lastIndexOf("/") + 1) : v;
                            Path path = Paths.get(fileName);
                            try {
                                mimeType = Files.probeContentType(path);
                            } catch (IOException e) {
                            }
                            if (StringUtils.startsWithIgnoreCase(v, WinCrawlerConstant.STRING_HTTP)) {
                                byte[] bytes = HttpUtils.INSTANCE.doFileGet(v);
                                specification.multiPart(k, fileName, bytes, mimeType);
                            }
                        }
                    } else if (RequestContentType.BINARY.equals(requestContentType)) {
                        // specification.
                    } else if (RequestContentType.EB.equals(requestContentType)) {
                        specification.config(RestAssured.config().encoderConfig(encoderConfig().encodeContentTypeAs(RequestContentType.EB.getName(), ContentType.JSON)));
                        specification.body(requestBody);
                    } else {
                        specification.config(RestAssured.config().encoderConfig(encoderConfig().encodeContentTypeAs(requestContentType.getName(), ContentType.TEXT)));
                        specification.body(requestBody);
                    }
                }
                //RequestMethod
                RequestMethod requestMethod = step.getRequestMethod();
                String requestUrl = step.getRequestUrl();
                if (RequestMethod.POST.equals(requestMethod)) {
                    response = specification.post(requestUrl);
                } else if (RequestMethod.GET.equals(requestMethod)) {
                    response = specification.get(requestUrl);
                } else if (RequestMethod.PUT.equals(requestMethod)) {
                    response = specification.put(requestUrl);
                } else if (RequestMethod.DELETE.equals(requestMethod)) {
                    response = specification.delete(requestUrl);
                } else {
                    response = specification.get(requestUrl);
                }
            } catch (Exception e) {
                log.error("[{}]第{}次请求失败", stepLogPrefix, index, e);
                if (needUseProxy) {
                    ProxyUtils.INSTANCE.changeAjaxProxy();
                }
            }
            if (response != null) {
                response.then().log().body();
                break;
            }
        }
        JSONObject ret = new JSONObject();
        ret.put("statusCode", response.getStatusCode());
        ret.put("headers", response.getHeaders());
        ret.put("cookies", response.getCookies());
        if (step instanceof Ocr) {
            ret.put("body", response.asByteArray());
        } else {
            ret.put("body", JsonUtils.INSTANCE.parseObject(response.asString()));
        }
        return ret;
    }

    private Object extractDataWithTpl(T step, TaskContext context, JSONObject response) throws Exception {
        if ((step instanceof Ocr) || StringUtils.isBlank(step.getTpl())) {
            return response;
        }
        Map<String, Object> localModel = new HashMap<>(response);
        if (MapUtils.isNotEmpty(context.getCurrentStoreParams())) {
            localModel.putAll(context.getCurrentStoreParams());
        }
        String data = paramsExecutor.render(step.getTpl(), localModel);
        return JsonUtils.INSTANCE.parseObject(data);
    }

}
